#include	"../include/OS.h"

#if defined(_WIN32)
#	include		<windows.h>
#	include		<direct.h>
#	include		<io.h>
#	pragma		comment(lib, "ole32.lib")
#else
#	include		<limits.h>
#	include		<dirent.h>
#	include		<unistd.h>
#	include		<sys/stat.h>
#	include		<uuid/uuid.h>
#endif

#include	<chrono>
#include	<cstring>
#include	<ctime>

DateTime operator+(const DateTime & start_time, double after) {
	double next = start_time.timestamp * 1000.0 + start_time.millisecond + after;
	time_t timestamp = (time_t)(next / 1000);
	int millisecond = (int)(next - timestamp * 1000.0) % 1000;
	struct tm local_time;

#if defined(_WIN32)
	localtime_s(&local_time, &timestamp);
#else
	localtime_r(&timestamp, &local_time);
#endif

	DateTime ret;
	ret.year = local_time.tm_year + 1900;
	ret.month = local_time.tm_mon + 1;
	ret.day = local_time.tm_mday;
	ret.week = local_time.tm_wday;
	ret.hour = local_time.tm_hour;
	ret.minute = local_time.tm_min;
	ret.second = local_time.tm_sec;
	ret.millisecond = millisecond;
	ret.timestamp = (int)timestamp;

	return std::move(ret);
}

double operator-(const DateTime & l, const DateTime & r) {
	return (l.timestamp - r.timestamp) * 1000.0 + l.millisecond - r.millisecond;
}

bool OS::Exists(const std::string & path) {
#if defined(_WIN32)
	return _access(path.c_str(), 0) == 0;
#else
	return access(path.c_str(), 0) == 0;
#endif
}

bool OS::CreateDir(const std::string & path) {
	std::vector<std::string> folders;
	std::string name;
	name.reserve(512);

	for (size_t i = 0; i < path.size(); ++i) {
		if (path[i] == '\\' || path[i] == '/') {
			if (name.back() != '/') {
				folders.push_back(name);
				name.push_back('/');
			}
		} else {
			name.push_back(path[i]);
		}
	}

	folders.push_back(name);

	for (auto & dir : folders) {
	#if defined(_WIN32)
		if (_access(dir.c_str(), 0) != 0) {
			if (_mkdir(dir.c_str()) != 0) return false;
		}
	#else
		if (access(dir.c_str(), 0) != 0) {
			if (mkdir(dir.c_str(), S_IRWXU | S_IRWXG | S_IRWXO) != 0) return false;
		}
	#endif
	}

	return true;
}

std::string OS::GetWorkDir() {
	char buf[512] = { 0 };
#if defined(_WIN32)
	_getcwd(buf, 512);
#else
	getcwd(buf, 512);
#endif
	return std::string(buf);
}

bool OS::ChangeWorkDir(const std::string & directory) {
#if defined(_WIN32)
	return (0 == _chdir(directory.c_str()));
#else
	return (0 == chdir(directory.c_str()));
#endif
}

std::string OS::GetFullPath(const std::string & path) {
	char buf[512] = { 0 };
#if defined(_WIN32)
	if (0 >= GetFullPathNameA(path.c_str(), 512, buf, NULL)) return path;
#else
	if (!realpath(path.c_str(), buf)) return path;
#endif
	return std::string(buf);
}

std::string OS::GetDirName(const std::string & file) {
	for (size_t i = file.length() - 1; i >= 0; --i) {
		if (file[i] == '/' || file[i] == '\\') {
			if (i != 0) return file.substr(0, i);
		}
	}

	return "./";
}

std::string OS::GetFileName(const std::string & file) {
	for (size_t i = file.length() - 1; i >= 0; --i) {
		if (file[i] == '/' || file[i] == '\\') {
			return file.substr(i + 1);
		}
	}

	return file;
}

std::vector<std::string> OS::GetFiles(const std::string & path, bool recursive /* = true */) {
	std::vector<std::string> files;
	std::vector<std::string> subdirs;

#if defined(_WIN32)
	WIN32_FIND_DATAA found;
	HANDLE result;

	std::string finder(path);
	finder.append("\\*.*");

	result = ::FindFirstFileA(finder.c_str(), &found);
	if (result == INVALID_HANDLE_VALUE)
		return files;

	while (::FindNextFileA(result, &found)) {
		std::string data(path);
		data.append("\\");
		data.append(found.cFileName);
		if (found.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
			if (!recursive || strcmp(found.cFileName, ".") == 0 || strcmp(found.cFileName, "..") == 0) {
				continue;
			} else {
				subdirs.push_back(data);
			}
		} else {
			files.push_back(data);
		}
	}

	::FindClose(result);
#else
	DIR *			dir;
	struct dirent *	file;
	struct stat		check;

	if (!(dir = ::opendir(path.c_str()))) return files;

	while (NULL != (file = ::readdir(dir))) {
		std::string data(path);
		data.append("/");
		data.append(file->d_name);

		if (strncmp(file->d_name, ".", 1) == 0)
			continue;
		else if (::stat(data.c_str(), &check) < 0)
			continue;

		if (S_ISDIR(check.st_mode)) {
			if (recursive) subdirs.push_back(data);
		} else if (S_ISREG(check.st_mode)) {
			files.push_back(data);
		}
	}

	::closedir(dir);
#endif

	for (auto & dir : subdirs) {
		auto subfiles = GetFiles(dir, true);
		for (auto & file : subfiles) files.push_back(file);
	}

	return files;
}

int OS::GetTimeZone() {
	time_t timestamp = time(NULL);
	struct tm local_time, gm_time;

#if defined(_WIN32)
	localtime_s(&local_time, &timestamp);
	gmtime_s(&gm_time, &timestamp);
#else
	localtime_r(&timestamp, &local_time);
	gm_time = (*gmtime(&timestamp));
#endif

	return local_time.tm_hour - gm_time.tm_hour;
}

double OS::Tick() {
	auto period = std::chrono::high_resolution_clock::now().time_since_epoch();
	return std::chrono::duration_cast<std::chrono::duration<double, std::ratio<1, 1000>>>(period).count();
}

DateTime OS::Now() {
	DateTime now;

#if defined(_WIN32)
	SYSTEMTIME sys_time;
	GetLocalTime(&sys_time);

	now.year = sys_time.wYear;
	now.month = sys_time.wMonth;
	now.day = sys_time.wDay;
	now.week = sys_time.wDayOfWeek;
	now.hour = sys_time.wHour;
	now.minute = sys_time.wMinute;
	now.second = sys_time.wSecond;
	now.millisecond = sys_time.wMilliseconds;
	now.timestamp = (int)time(NULL);
#else
	struct tm local_time;
	struct timespec detail;

	clock_gettime(CLOCK_REALTIME, &detail);
	localtime_r(&detail.tv_sec, &local_time);

	now.year = local_time.tm_year + 1900;
	now.month = local_time.tm_mon + 1;
	now.day = local_time.tm_mday;
	now.week = local_time.tm_wday;
	now.hour = local_time.tm_hour;
	now.minute = local_time.tm_min;
	now.second = local_time.tm_sec;
	now.millisecond = detail.tv_nsec / 1000000;
	now.timestamp = (int)detail.tv_sec;
#endif

	return std::move(now);
}

DateTime OS::ParseDateTime(int year, int month, int day, int hour, int minute, int second) {
	struct tm local_time;
	DateTime ret;

	local_time.tm_year = year - 1900;
	local_time.tm_mon = month - 1;
	local_time.tm_mday = day;
	local_time.tm_hour = hour;
	local_time.tm_min = minute;
	local_time.tm_sec = second;

	ret.timestamp = (int)mktime(&local_time);
	ret.year = local_time.tm_year + 1900;
	ret.month = local_time.tm_mon + 1;
	ret.day = local_time.tm_mday;
	ret.week = local_time.tm_wday;
	ret.hour = local_time.tm_hour;
	ret.minute = local_time.tm_min;
	ret.second = local_time.tm_sec;
	ret.millisecond = 0;

	return std::move(ret);
}

std::string OS::CreateID() {
	char hex[64] = { 0 };
	uint32_t data[4] = { 0 };

#if defined(_WIN32)
	GUID guid;

	::CoInitialize(NULL);
	::CoCreateGuid(&guid);
	::CoUninitialize();

	memcpy(data, &guid, sizeof(uint32_t) * 4);
#else
	uuid_t uuid;
	uuid_generate(uuid);
	memcpy(data, &uuid, sizeof(uint32_t) * 4);
#endif

	snprintf(hex, 64, "%08X%08X%08X%08X", data[0], data[1], data[2], data[3]);
	return std::string(hex);
}